使用 Eleventy 搭建静态博客
本文学自《Lesson 11: Blog feeds, tags and pagination | Learn Eleventy From Scratch》,介绍了如何使用 Eleventy 搭建一个个人静态博客。
博客内容位于 src/posts
下。内部包含 Markdown 博客文章,并且每篇文章中都包含元数据:
---
title: 'Why cross-cultural design really matters'
date: '2020-04-01'
tags: ['Culture', 'Design Thinking']
---
其中:制定了 date 日期。如果不指定的话,Eleventy 会采用文件在文件系统中的创建时间。除了 date
外,还可以取值包括:
Last Modified
:这将始终是您最后一次编辑文件的时间。Created
:这与省略date
相同。
创建 Collection
利用《Eleventy Collection》特性,将所有博客文章加入 blog
集合当中。在 .eleventy.js
中添加:
// Returns a collection of blog posts in reverse date order
config.addCollection('blog', collection => {
return [...collection.getFilteredByGlob('./src/posts/*.md')].reverse();
});
创建博客列表
在《Eleventy Layout》目录中创建一个布局 feed.html
(feed.njk
):
{% extends "layouts/base.html" %}
{% set pageHeaderTitle = title %}
{% set pageHeaderSummary = content %}
{% set postListItems = pagination.items %}
{% block content %}
<article>
{% include "partials/page-header.html" %}
{% include "partials/post-list.html" %}
</article>
{% endblock %}
其中:
- 创建了 3 个变量:pageHeaderTitle、pageHeaderSummary、postListItems。
content
则是一个特殊变量,它代表了模板中除 Front Matter 外的全部内容。- title 和 pagination 均为 Front Matter 中设定的。
通过变量声明,在 partial 中也能够访问到这些变量了。
创建 Partials
首先是 page-header.html
(page-header.njk
):
<div class="[ page-header ] [ bg-light-glare ]">
<div class="[ wrapper ] [ flow ]">
<h1 class="[ page-header__heading ] [ headline ]" data-highlight="primary">
{{ pageHeaderTitle }}
</h1>
{% if pageHeaderSummary %}
<div class="[ page-header__summary ] [ measure-long ]">
{{ pageHeaderSummary | safe }}
</div>
{% endif %}
</div>
</div>
接下来是文章列表 post-list.html
(post-list.njk
):
<div class="[ dot-shadow panel ] [ bg-secondary-glare ]" id="post-list">
<div class="[ wrapper ] [ flow flow-space-700 ]">
{% if postListHeadline %}
<h2 class="[ headline ] [ measure-micro ]" data-highlight="primary">
{{ postListHeadline }}
</h2>
{% endif %}
<div>
<ol class="[ post-list ] [ flow ]">
{% for item in postListItems %}
<li class="[ post-list__item ] [ leading-tight measure-long ]">
<a href="{{ item.url }}" class="post-list__link">{{ item.data.title }}</a>
</li>
{% endfor %}
</ol>
</div>
</div>
</div>
创建文章列表页数据
在 src
目录下创建一个 blog.md
,它表示列表页的数据,根据该文件将会创建 blog.html
,其内容主要包含元数据:
---
title: 'The Issue 33 Blog'
layout: 'layouts/feed.html'
pagination:
data: collections.blog
size: 5
permalink: 'blog{% if pagination.pageNumber > 0 %}/page/{{ pagination.pageNumber }}{% endif %}/index.html'
paginationPrevText: 'Newer posts'
paginationNextText: 'Older posts'
paginationAnchor: '#post-list'
---
The latest articles from around the studio, demonstrating our design
thinking, strategy and expertise.
其中:
- pagination 中的 data 是从《Eleventy Collection》中而来。
paginationPrevText
、paginationNextText
均为 Eleventy 中的特殊变量- permalink:指定了分页下的多页面设置。
对于 permalink,指定这段代码后,分页 url 路径为:
blog
blog/page/1
blog/page/2
如果不指定 permalink,默认的分页 url 路径为:
blog
blog/1
blog/2
创建分页 Partials
前面能够生成分页的多页面,还需要在页面模板内打通连接。创建 pagination.html
(pagination.njk
):
{# Only renders this section if there are links to render #}
{% if pagination.href.next or pagination.href.previous %}
<footer class="[ pagination ] [ dot-shadow panel ] [ bg-light-glare font-sans weight-bold ]">
<div class="wrapper">
<nav class="pagination__inner" aria-label="Pagination links">
{% if pagination.href.previous %}
<a href="{{ pagination.href.previous }}{{ paginationAnchor }}" data-direction="backwards">
<span>{{ paginationPrevText if paginationPrevText else 'Previous' }}</span>
</a>
{% endif %}
{% if pagination.href.next %}
<a href="{{ pagination.href.next }}{{ paginationAnchor }}" data-direction="forwards">
<span>{{ paginationNextText if paginationNextText else 'Next' }}</span>
</a>
{% endif %}
</nav>
</div>
</footer>
{% endif %}
创建 Tab 标签页
Eleventy 对标签有特殊的支持,会自动为 Front Matter 的 tag 创建《Eleventy Collection》。
在 src
文件夹中创建一个名为 tags.md
的新文件:
---
title: 'Tag Archive'
layout: 'layouts/feed.html'
pagination:
data: collections
size: 1
alias: tag
filter: ['all', 'nav', 'blog', 'work', 'featuredWork', 'people', 'rss']
permalink: '/tag/{{ tag | slug }}/'
---
这里直接取了所有 Collections 的数据,并且通过 filter 排除了一些专门功能类的 Collections,这样剩下的就都是 tags。
然后巧妙的设置分页个数为 1,这样每一页将是一个 Tag。
之后重写 feed.html
,如果是 tag 页,不展示分页 Partials:
{% extends "layouts/base.html" %}
{% set pageHeaderTitle = title %}
{% set pageHeaderSummary = content %}
{% set postListItems = pagination.items %}
{# If this is a tag, grab those items instead as one large collection #}
{% if tag %}
{% set postListItems = collections[tag] %}
{% set pageHeaderTitle = 'Blog posts filed under “' + tag + '”' %}
{% endif %}
{% block content %}
<article>
{% include "partials/page-header.html" %}
{% include "partials/post-list.html" %}
{# If we leave pagination in for tags, the next and prev links will
link to tags and be rather confusing, so don't render in that situation #}
{% if not tag %}
{% include "partials/pagination.html" %}
{% endif %}
</article>
{% include "partials/cta.html" %}
{% endblock %}
并且,在上述代码中:
{% set postListItems = collections[tag] %}
:获取到了 Tag 下所有的帖子{% set pageHeaderTitle = 'Blog posts filed under “' + tag + '”' %}
:获取到了 Tag 名称
博客详情页
本节继续课程《Lesson 12: Blog post view, directory data and filters | Learn Eleventy From Scratch》开始实现博客的文章详情页。在《Eleventy Layout》下创建 post.html
:
{% extends "layouts/base.html" %}
{% set pageHeaderTitle = title %}
{# Render post date and any tags that this post has been filed under #}
{% set pageHeaderSummary %}
<time datetime="{{ date | w3DateFilter }}">{{ date | dateFilter }}</time>
{% if tags %}
<p class="visually-hidden" id="tags-desc">Tags that this post has been filed under.</p>
<ul class="tags-list" aria-describedby="tags-desc">
{% for tag in tags %}
<li>
<a href="/tag/{{ tag | slug }}/">#{{ tag | title | replace(' ', '') }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endset %}
{% block content %}
<article>
{% include "partials/page-header.html" %}
<div class="[ page-content ] [ flow wrapper ] [ flow-space-700 gap-top-700 ]">
{{ content | safe }}
</div>
</article>
{% include "partials/cta.html" %}
{% endblock %}
其中:
- 对于 Tag 的 url 处理
/tag/{{ tag | slug }}/
,使用了一个| slug
的 filter,他可以将 Design Thinking 变为 design-thinking。
使用 filter 处理时间
在上述详情的模板中,存在两个 filter:dateFilter 和 w3DateFilter,在本节中开发这两个 filter。创建一个 src/filters 目录,先创建 date-filter.js
(注意文件名与 Filter 名的映射关系):
const moment = require('moment');
module.exports = value => {
const dateObject = moment(value);
return `${dateObject.format('Do')} of ${dateObject.format('MMMM YYYY')}`;
};
这里使用 moment 库对日期进行处理,将日期转换为 1st of April 2020
的格式。
注:安装依赖,npm install moment
。
第二个模板 w3-date-filter.js
:
module.exports = value => {
const dateObject = new Date(value);
return dateObject.toISOString();
};
最后,在 .eleventy.js
中添加注册 filter:
// Filters
const dateFilter = require('./src/filters/date-filter.js');
const w3DateFilter = require('./src/filters/w3-date-filter.js');
//...
// Add filters
config.addFilter('dateFilter', dateFilter);
config.addFilter('w3DateFilter', w3DateFilter);
批量设置模板
Eleventy 支持向目录中所有内容统一指定模板。比如,所有博客内容都位于 posts 下,创建 posts.json
(与目录同名,表示统一配置):
{
"layout": "layouts/post.html",
"permalink": "/blog/{{ title | slug }}/index.html"
}
这种叫做目录数据文件。如果内容文件自身仍然指定了 layout,将会覆盖目录级别的通用设置。
其中,还指定了 permalink,默认情况下,Eleventy 将使用文件名及其目录来创建永久链接。这意味着,如果没有我们当前的设置,文件名为 my-lovely-post.md
的帖子将具有 /posts/my-lovely-post/index.html
的永久链接。不过,我们希望所有帖子都位于博客部分,因此我们在 permalink
的值中所做的是将 /blog
设置为根,然后从帖子的标题。
内容推荐
在本节中,将在详情页中添加更多博客内容推荐功能,会展示 3 个随即挑选的帖子。
在 eleventy-from-scratch/src/_data/helpers.js
中添加代码:
/**
* Filters out the passed item from the passed collection
* and randomises and limits them based on flags
*
* @param {Array} collection The 11ty collection we want to take from
* @param {Object} item The item we want to exclude (often current page)
* @param {Number} limit=3 How many items we want back
* @param {Boolean} random=true Wether or not this should be randomised
* @returns {Array} The resulting collection
*/
getSiblingContent(collection, item, limit = 3, random = true) {
let filteredItems = collection.filter(x => x.url !== item.url);
if (random) {
let counter = filteredItems.length;
while (counter > 0) {
// Pick a random index
let index = Math.floor(Math.random() * counter);
counter--;
let temp = filteredItems[counter];
// Swap the last element with the random one
filteredItems[counter] = filteredItems[index];
filteredItems[index] = temp;
}
}
// Lastly, trim to length
if (limit > 0) {
filteredItems = filteredItems.slice(0, limit);
}
return filteredItems;
}
在 eleventy-from-scratch/src/_includes/layouts/post.html
中添加一个变量获取推荐内容:
{# Grab other posts that aren’t this one for the 'more from the blog' feed #}
{% set recommendedPosts = helpers.getSiblingContent(collections.blog, page) %}
之后,在相应位置添加:
{% if recommendedPosts %}
<footer class="recommended-posts">
{% set postListItems = recommendedPosts %}
{% set postListHeadline = 'More from the blog' %}
{% include "partials/post-list.html" %}
</footer>
{% endif %}
本文作者:Maeiee
本文链接:使用 Eleventy 搭建静态博客
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!